from .version import __version__
from io import BytesIO, StringIO
import struct
import base64
import os
from copy import copy,deepcopy
import json
import numpy
import uuid
from collections import defaultdict
from contextlib import contextmanager
from time import time
import re

import logging
log = logging.getLogger(__name__)


defaultGltfMaterial = {
    "pbrMetallicRoughness": {
        "baseColorFactor": [round(val / 255., 4) for val in (244,244,244)] + [1.0],
        "metallicFactor": 0.1,
        "roughnessFactor": 0.7,
    },
    'alphaMode': 'BLEND',
    'alphaCutoff': 1.0
}

def getArrayFromMatrix(matrix):
    return [matrix.A11, matrix.A21, matrix.A31, matrix.A41, matrix.A12, matrix.A22, matrix.A32, matrix.A42, matrix.A13, matrix.A23, matrix.A33, matrix.A43, matrix.A14, matrix.A24, matrix.A34, matrix.A44]

def getIdentityMatrix():
    return [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]


@contextmanager
def measure_time(log, name):
    start_time = time()
    yield
    taken = time() - start_time
    log.debug("    %-25s (took: %gms)", name, round(taken * 1000, 3))

class WebGL:
    """
    Enumeration container (nothing special).

    .. doctest::

    This class purely exists to make the code more readable.

    All enumerations transcribed from
    `the spec' <https://github.com/KhronosGroup/glTF/tree/master/specification/2.0>`_
    where needed.
    """

    # accessor.componentType
    BYTE = 5120
    UNSIGNED_BYTE = 5121
    SHORT = 5122
    UNSIGNED_SHORT = 5123
    UNSIGNED_INT = 5125
    FLOAT = 5126

    # bufferView.target
    ARRAY_BUFFER = 34962
    ELEMENT_ARRAY_BUFFER = 34963

    # mesh.primative.mode
    POINTS = 0
    LINES = 1
    LINE_LOOP = 2
    LINE_STRIP = 3
    TRIANGLES = 4
    TRIANGLE_STRIP = 5
    TRIANGLE_FAN = 6

def _list3_min(l_a, l_b):
    return [min((a, b)) for (a, b) in zip(l_a if l_a else l_b, l_b)]


def _list3_max(l_a, l_b):
    return [max((a, b)) for (a, b) in zip(l_a if l_a else l_b, l_b)]


class ShapeBuffer(object):
    """
    Write byte buffer for a set of polygons

    To create a buffer for a single polygon:

    .. doctest::

        >>> from cqparts.codec.gltf import ShapeBuffer
        >>> sb = ShapeBuffer()

        >>> # Populate data
        >>> sb.add_vertex(0, 0, 0)  # [0]
        >>> sb.add_vertex(1, 0, 0)  # [1]
        >>> sb.add_vertex(0, 1, 0)  # [2]
        >>> sb.add_poly_index(0, 1, 2)

        >>> # write to file
        >>> with open('single-poly.bin', 'wb') as fh:  # doctest: +SKIP
        ...     for chunk in sb.buffer_iter():  # doctest: +SKIP
        ...         fh.write(chunk)  # doctest: +SKIP

        >>> # get sizes (relevant for bufferViews, and accessors)
        >>> (sb.vert_len, sb.vert_offset, sb.vert_size)
        (36L, 0, 3L)
        >>> (sb.idx_len, sb.idx_offset, sb.idx_size)
        (3L, 36L, 3L)
    """
    def __init__(self, max_index=0xff):
        """
        :param max_index: maximum index number, if > 65535, 4-byte integers are used
        :type max_index: :class:`long`
        """
        self.vert_data = BytesIO()  # vertex positions
        self.idx_data = BytesIO()  # indices connecting vertices into polygons

        # vertices min/max2
        self.vert_min = None
        self.vert_max = None

        # indices number format
        self.max_index = max_index
        if max_index > 0xffff:
            (self.idx_fmt, self.idx_type, self.idx_bytelen) = ('<I', WebGL.UNSIGNED_INT, 4)
        elif max_index > 0xff:
            (self.idx_fmt, self.idx_type, self.idx_bytelen) = ('<H', WebGL.UNSIGNED_SHORT, 2)
        else:
            (self.idx_fmt, self.idx_type, self.idx_bytelen) = ('B', WebGL.UNSIGNED_BYTE, 1)

    @property
    def vert_len(self):
        """
        Number of bytes in ``vert_data`` buffer.
        """
        return self.vert_data.tell()

    @property
    def vert_offset(self):
        """
        Offset (in bytes) of the ``vert_data`` buffer.
        """
        return 0

    @property
    def vert_size(self):
        r"""
        Size of ``vert_data`` in groups of 3 floats
        (ie: number of :math:`3 \times 4` byte groups)

        See `Accessor Element Size <https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#accessor-element-size>`_
        in the glTF docs for clarification.
        """
        # size of position buffer, in groups of 3 floats
        return self.vert_len / (3 * 4)

    @property
    def idx_len(self):
        """
        Number of bytes in ``idx_data`` buffer.
        """
        return self.idx_data.tell()

    @property
    def idx_offset(self):
        """
        Offset (in bytes) of the ``idx_data`` buffer.
        """
        # after vert_data
        return self.vert_offset + self.vert_len

    @property
    def idx_size(self):
        """
        Number of ``idx_data`` elements.
        (ie: number of 2 or 4 byte groups)

        See `Accessor Element Size <https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#accessor-element-size>`_
        in the glTF docs for clarification.
        """
        return self.idx_len / self.idx_bytelen

    def __len__(self):
        return self.vert_len + self.idx_len

    def add_vertex(self, x, y, z):
        """
        Add a ``VEC3`` of ``floats`` to the ``vert_data`` buffer
        """
        self.vert_data.write(
            struct.pack('<f', x) +
            struct.pack('<f', y) +
            struct.pack('<f', z)
        )

        # retain min/max values
        self.vert_min = _list3_min(self.vert_min, (x, y, z))
        self.vert_max = _list3_max(self.vert_max, (x, y, z))

    def add_poly_index(self, i, j, k):
        """
        Add 3 ``SCALAR`` of ``uint`` to the ``idx_data`` buffer.
        """
        self.idx_data.write(
            struct.pack(self.idx_fmt, i) +
            struct.pack(self.idx_fmt, j) +
            struct.pack(self.idx_fmt, k)
        )

    def buffer_iter(self, block_size=1024):
        """
        Iterate through chunks of the vertices, and indices buffers seamlessly.

        .. note::

            To see a usage example, look at the :class:`ShapeBuffer` description.
        """
        streams = (
            self.vert_data,
            self.idx_data,
        )

        # Chain streams seamlessly
        for stream in streams:
            stream.seek(0)
            while True:
                chunk = stream.read(block_size)
                if chunk:
                    yield chunk
                else:
                    break

        # When complete, each stream position should be reset;
        #   back to the end of the stream.

    def read(self):
        """
        Read buffer out as a single stream.

        .. warning::

            Avoid using this function!

            **Why?** This is a *convenience* function; it doesn't encourage good
            memory management.

            All memory required for a mesh is duplicated, and returned as a
            single :class:`str`. So at best, using this function will double
            the memory required for a single model.

            **Instead:** Wherever possible, please use :meth:`buffer_iter`.
        """
        buffer = BytesIO()
        for chunk in self.buffer_iter():
            log.debug('buffer.write(%r)', chunk)
            buffer.write(chunk)
        buffer.seek(0)
        return buffer.read()

class Gltf:
    scale = 0.001  # mm to meters
    TEMPLATE = {
        # Static values
        "asset": {
            "generator": "cadparser%s" % __version__,
            "version": "2.0"  # glTF version
        },
        "scene": 0,

        # Populated by adding parts
        "scenes": [{"nodes": [0]}],
        "nodes": [
            {
                "children": [],  # will be appended to before writing to file
                # scene rotation to suit glTF coordinate system
                # ref: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#coordinate-system-and-units
                "matrix": [
                    1.0 * scale, 0.0, 0.0, 0.0,
                    0.0, 0.0,-1.0 * scale, 0.0,
                    0.0, 1.0 * scale, 0.0, 0.0,
                    0.0, 0.0, 0.0, 1.0,
                ],
            },
        ],
        "meshes": [],
        "accessors": [],
        "materials": [],
        "bufferViews": [],
        "buffers": [],
    }

    # error tolerance of vertices to true face value, only relevant for curved surfaces.
    tolerance = 0.05
    def __init__(self, viewObjects, filename, replace):
        # Initialize
        self.gltf_dict = deepcopy(self.TEMPLATE)
        self.viewObjects = viewObjects
        self.filename = filename
        self.replace = replace
        self.scene_min = None
        self.scene_max = None
        self.results = {}

    def getRealName(self,name):
        if(self.replace):
            r = re.compile('.+\$')
            res = r.search(name)
            if not res:
                return name
            else:
                mainName = name[res.start():res.end() - 1]
                realName = self.replace.getBackReplaced(mainName)
                if not realName:
                    return name
                else:
                    subName = name[res.end():]
                    return realName + subName
        else:
            return name

    def parse(self):
        """
        :param filename: name of ``.gltf`` file to export
        :type filename: :class:`str`
        :param embed: if True, binary content is embedded in json object.
        :type embed: :class:`bool`
        """

        def add(obj, filename, parent_node_index=0):
            split = os.path.splitext(filename)
            if len(obj.children) > 0:
                # --- Assembly

                # Add empty node to serve as a parent
                node_index = len(self.gltf_dict['nodes'])
                node = {}
                node['matrix'] = getArrayFromMatrix(obj.localMatrix)#list(obj.localMatrix.A)
                node['name'] = self.getRealName(obj.name)
                node['extensions'] = {'TUOID': str(uuid.uuid4())}
                self.gltf_dict['nodes'].append(node)

                # Add this node to its parent
                parent_node = self.gltf_dict['nodes'][parent_node_index]
                parent_node['children'] = parent_node.get('children', []) + [node_index]

                count2 = 0
                for child in obj.children:
                    # Recursively add children
                    add(
                        child,
                        filename="%s.%s%s" % (split[0], str(count2), split[1]),
                        parent_node_index=node_index,
                    )
                    count2 = count2 + 1

            else:
                # --- Part
                self.add_part(
                    obj,
                    filename=filename,
                    parent_idx=parent_node_index,
                )

        #split = os.path.splitext(filename)
        #if self.obj.world_coords is None:
        #    self.obj.world_coords = CoordSystem()

        for obj in self.viewObjects:
            add(
                obj=obj,
                filename="%s.bin" % self.filename,
            )

        blob = StringIO()
        blob.write(json.dumps(self.gltf_dict, indent=2, sort_keys=True))
        self.results[self.filename + '.gltf'] = blob
        blob.seek(0)

        #r_mesh = pyrender.Mesh(self.primitives)
       
        #with open(filename, 'w') as fh:
        #    fh.write(json.dumps(self.gltf_dict, indent=2, sort_keys=True))

    @classmethod
    def coordsys_dict(cls, coord_sys, matrix=True):
        """
        Return coordinate system as
        `gltf node transform <https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#transformations>`_

        :param coord_sys: Coordinate system to transform
        :type coord_sys: :class:`CoordSystem <cqparts.utils.geometry.CoordSystem>`
        :return: node transform keys & values
        :rtype: :class:`dict`
        """
        node_update = {}
        if matrix:
            m = coord_sys.local_to_world_transform  # FreeCAD.Base.Matrix
            node_update.update({
                # glTF matrix is column major; needs to be tranposed
                'matrix': m.transposed().A,
            })
        else:
            raise NotImplementedError("only matrix export is supported (for now)")
            # The plan is to support something more like:
            #   {
            #       "rotation": [0, 0, 0, 1],
            #       "scale": [1, 1, 1],
            #       "translation": [-17.7082, -11.4156, 2.0922]
            #   }
            # This is preferable since it's more human-readable.
        return node_update

    @classmethod
    def part_mesh(cls, part):
        """
        Convert a part's object to a mesh.

        :param part: part being converted to a mesh
        :type part: :class:`Part <cqparts.Part>`
        :param world: if True, world coordinates are used
        :type world: :class:`bool`
        :return: list of (<vertices>, <indexes>)
        :rtype: :class:`tuple`

        Returned mesh format::

            <return value> = (
                [FreeCAD.Base.Vector(x, y, z), ... ],  # list of vertices
                [(i, j, k), ... ],  # indexes of vertices making a polygon
            )
        """
        with measure_time(log, 'buffers.part_mesh'):
            tess = None
            wireDis = None
            workplane = part.local_obj  # cadquery.CQ instance
            shape = workplane.val()  # expecting a cadquery.Solid instance
            if(hasattr(shape, 'tessellate')):
                #tess = shape.tessellate(cls.tolerance)
                tess = shape.tessellate(0.05, 0.5)
            if(hasattr(shape, 'getWireDiscretize')):
                #wireDis = shape.getWireDiscretize(0.05)
                try:
                    wireDis = shape.getWireDiscretize(0.05)
                except Exception as e:
                    log.error(str(e))
            return (tess, wireDis)

    @classmethod
    def part_buffer(cls, part):
        """
        Export part's geometry as a
        `glTF 2.0 <https://github.com/KhronosGroup/glTF/tree/master/specification/2.0>`_
        asset binary stream.

        :param world: if True, use world coordinates, otherwise use local
        :type world: :class:`bool`
        :return: byte sream of exported geometry
        :rtype: :class:`BytesIO`

            {'uri': 'data:application/octet-stream;base64,AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAC/AAECAQMCBAUGBAcFAwcCAgcEAAUBBgUAAwEHBwEFBAACBgAE'}
        """
        # binary save done here:
        #    https://github.com/KhronosGroup/glTF-Blender-Exporter/blob/master/scripts/addons/io_scene_gltf2/gltf2_export.py#L112

        # Get shape's mesh
        #(tess, wireDis) = cls.part_mesh(part)
        tess = part.tess
        wireDis = part.wireDis
        vetsGroup = None
        indexesGroup = None
        colors = None
        indices = None
        vertices = None
        if tess:
            (vertices, indices, vetsGroup, indexesGroup, colors) = tess
        if not vertices:
            vertices = []
        meshLength = None
        wireLength = None
        meshLength = len(vertices)
        if wireDis:
            wireLength = len(wireDis)
            vertices.extend(wireDis)

        if len(vertices) < 1:
            return (None, None, None, None, None, None)

        #if len(indices) < 1:
        #    return (None, None, None, None)

            # Create ShapeBuffer
        if indices:
            buff = ShapeBuffer(
                max_index=numpy.matrix(indices).max(),
            )
        else:
            buff = ShapeBuffer(
                max_index = len(vertices)
            )

        # Push mesh to ShapeBuffer
        for vert in vertices:
            buff.add_vertex(vert.x, vert.y, vert.z)
        if indices:
            for (i, j, k) in indices:
                buff.add_poly_index(i, j, k)
        return (buff, vetsGroup, indexesGroup, colors, meshLength, wireLength)

    def add_part(self, part, filename=None, parent_idx=0):
        """
        Adds the given ``part`` to ``self.gltf_dict``.

        :param part: part to add to gltf export
        :type part: :class:`Part <cqparts.Part>`
        :param filename: name of binary file to store buffer, if ``None``,
                         binary data is embedded in the *buffer's 'uri'*
        :type filename: :class:`str`
        :param name: name given to exported mesh (optional)
        :type name: :class:`str`
        :param parent_idx: index of parent node (everything is added to a hierarchy)
        :type parent_idx: :class:`int`
        :return: information about additions to the gltf dict
        :rtype: :class:`dict`

        **Return Format:**

        The returned :class:`dict` is an account of what objects were added
        to the gltf dict, and the index they may be referenced::

            <return format> = {
                'buffers':     [(<index>, <object>), ... ],
                'bufferViews': [(<index>, <object>), ... ],
                'accessors':   [(<index>, <object>), ... ],
                'materials':   [(<index>, <object>), ... ],
                'meshes':      [(<index>, <object>), ... ],
                'nodes':       [(<index>, <object>), ... ],
            }

        .. note::

            The format of the returned :class:`dict` **looks similar** to the gltf
            format, but it **is not**.
        """
        info = defaultdict(list)

        log.debug("gltf export: %r", part)

        # ----- Adding to: buffers
        with measure_time(log, 'buffers'):
            #result1 = self.part_buffer(part)
            (buff, vetsGroup, indexesGroup, colors, meshLength, wireLength) = self.part_buffer(part)

            if not buff:
                return None
            self.scene_min = _list3_min(
                self.scene_min,
                (buff.vert_min[0] + part.globalMatrix.A14, buff.vert_min[1] + part.globalMatrix.A24, \
buff.vert_min[2] + part.globalMatrix.A34 )
            )

            self.scene_max = _list3_max(
                self.scene_max,
                (buff.vert_max[0] + part.globalMatrix.A14, buff.vert_max[1] + part.globalMatrix.A24, \
buff.vert_max[2] + part.globalMatrix.A34 )
            )

            buffer_index = len(self.gltf_dict['buffers'])
            buffer_dict = {
                "byteLength": len(buff),
            }
            if filename:
                blob = BytesIO()
                for chunk in buff.buffer_iter():
                    blob.write(chunk)
                self.results[filename] = blob
                blob.seek(0)
                buffer_dict['uri'] = os.path.basename(filename)
            else:
                # embed buffer data in URI
                buffer_dict['uri'] = "data:{mimetype};base64,{data}".format(
                    mimetype="application/octet-stream",
                    data=base64.b64encode(buff.read()).decode('ascii'),
                )

            self.gltf_dict['buffers'].append(buffer_dict)
            info['buffers'].append((buffer_index, buffer_dict))
      
        vetsCountOffset = 0
        indexesCountOffset = 0
        accessor_index_indices_group = []
        accessor_index_vertices_group = []
        #print('test print:', indexesGroup, vetsGroup, colors, meshLength, wireLength)
        if vetsGroup:
            with measure_time(log, 'bufferViews'):
                for i in range(len(vetsGroup)):
                    vetsCount = vetsGroup[i]
                    indexesCount = indexesGroup[i] 
                    # ----- Adding: bufferViews
                    bufferView_index = len(self.gltf_dict['bufferViews'])

                    # vertices view
                    view = {
                        "buffer": buffer_index,
                        "byteOffset": buff.vert_offset + vetsCountOffset*12,
                        "byteLength": vetsCount*12,#buff.vert_len + vetsCount*12,
                        "byteStride": 12,
                        "target": WebGL.ARRAY_BUFFER,
                    }
                    self.gltf_dict['bufferViews'].append(view)
                    bufferView_index_vertices = bufferView_index
                    info['bufferViews'].append((bufferView_index_vertices, view))

                    
                    if buff.idx_type == 5121 or buff.idx_type == 5120:
                        idx_item_size = 1
                    elif buff.idx_type == 5122 or buff.idx_type == 5123:
                        idx_item_size = 2
                    elif buff.idx_type == 5125 or buff.idx_type == 5126:
                        idx_item_size = 4


                    # indices view
                    view = {
                        "buffer": buffer_index,
                        "byteOffset": buff.idx_offset + indexesCountOffset*idx_item_size,
                        "byteLength": indexesCount*idx_item_size,#buff.idx_len + indexesCount,
                        "target": WebGL.ELEMENT_ARRAY_BUFFER,
                    }
                    self.gltf_dict['bufferViews'].append(view)
                    bufferView_index_indices = bufferView_index + 1
                    info['bufferViews'].append((bufferView_index_indices, view))

                    # ----- Adding: accessors
                    #with measure_time(log, 'accessors'):
                    accessor_index = len(self.gltf_dict['accessors'])

                    # vertices accessor
                    accessor = {
                        "bufferView": bufferView_index_vertices,
                        "byteOffset": 0,
                        "componentType": WebGL.FLOAT,
                        "count": vetsCount,#buff.vert_size,
                        "min": [v - 0.1 for v in buff.vert_min],
                        "max": [v + 0.1 for v in buff.vert_max],
                        "type": "VEC3",
                    }
                    self.gltf_dict['accessors'].append(accessor)
                    accessor_index_vertices = accessor_index
                    accessor_index_vertices_group.append(accessor_index_vertices)
                    info['accessors'].append((accessor_index_vertices, accessor))

                    # indices accessor
                    accessor = {
                        "bufferView": bufferView_index_indices,
                        "byteOffset": 0,
                        "componentType": buff.idx_type,
                        "count": indexesCount,#buff.idx_size,
                        "type": "SCALAR",
                    }
                    self.gltf_dict['accessors'].append(accessor)
                    accessor_index_indices = accessor_index + 1
                    accessor_index_indices_group.append(accessor_index_indices)
                    info['accessors'].append((accessor_index_indices, accessor))
                    vetsCountOffset += vetsCount
                    indexesCountOffset += indexesCount
        # ----- Adding: Wire accessor and bufferViews
        if meshLength != None:
            # ----- Adding: bufferViews
            bufferView_index = len(self.gltf_dict['bufferViews'])

            # vertices view
            view = {
                "buffer": buffer_index,
                "byteOffset": buff.vert_offset + meshLength*12,
                "byteLength": wireLength*12,#buff.vert_len + vetsCount*12,
                "byteStride": 12,
                "target": WebGL.ARRAY_BUFFER,
            }

            self.gltf_dict['bufferViews'].append(view)
            bufferView_index_vertices_wire = bufferView_index
            info['bufferViews'].append((bufferView_index_vertices_wire, view))


            # ----- Adding: accessors
            #with measure_time(log, 'accessors'):
            accessor_index = len(self.gltf_dict['accessors'])

            # vertices accessor
            accessor = {
                "bufferView": bufferView_index_vertices_wire,
                "byteOffset": 0,
                "componentType": WebGL.FLOAT,
                "count": wireLength,#buff.vert_size,
                "min": [v - 0.1 for v in buff.vert_min],
                "max": [v + 0.1 for v in buff.vert_max],
                "type": "VEC3",
            }
            self.gltf_dict['accessors'].append(accessor)
            accessor_index_vertices_wire = accessor_index
            info['accessors'].append((accessor_index_vertices_wire, accessor))

        

        # ----- Adding: materials
        material_index_group = []
        if colors:
            with measure_time(log, 'materials'):
                for j in range(len(colors)):
                    color = colors[j]
                    material_index = len(self.gltf_dict['materials'])
                    material = deepcopy(defaultGltfMaterial)#part._render.gltf_material
                    material["pbrMetallicRoughness"]["baseColorFactor"][0] = color[0]
                    material["pbrMetallicRoughness"]["baseColorFactor"][1] = color[1]
                    material["pbrMetallicRoughness"]["baseColorFactor"][2] = color[2]
                    material["pbrMetallicRoughness"]["baseColorFactor"][3] = 1.0 if color[3] == 0 else color[3]
                    self.gltf_dict['materials'].append(material)
                    info['materials'].append((material_index, material))
                    material_index_group.append(material_index)
        else:
            with measure_time(log, 'materials'):
                material_index = len(self.gltf_dict['materials'])
                material = deepcopy(defaultGltfMaterial)#part._render.gltf_material
                self.gltf_dict['materials'].append(material)
                info['materials'].append((material_index, material))
                material_index_group.append(material_index)

        # ----- Adding: wire materials
        if meshLength != None:
            material_index_wire = len(self.gltf_dict['materials'])
            material = {
                "pbrMetallicRoughness": {
                    "baseColorFactor": [0,0,0,1],
                },
                'alphaMode': 'BLEND',
                'alphaCutoff': 1.0,
                #"name": "red",
            }
            self.gltf_dict['materials'].append(material)
            info['materials'].append((material_index_wire, material))

        # ----- Adding: meshes
        #mesh_index_group = []
        #for j in range(len(accessor_index_vertices_group)):
        with measure_time(log, 'meshes'):
            mesh_index = len(self.gltf_dict['meshes'])
            mesh = {
                "primitives": []
            }
            for j in range(len(accessor_index_vertices_group)):
                accessor_index_indices = accessor_index_indices_group[j]
                accessor_index_vertices = accessor_index_vertices_group[j]
                mesh["primitives"].append({
                    "attributes": {
                        "POSITION": accessor_index_vertices,
                    },
                    "indices": accessor_index_indices,
                    "mode": WebGL.TRIANGLES,
                    "material": material_index_group[0] if \
len(material_index_group) == 1 else material_index_group[j]#material_index,
                })
            #mesh = {
            #    "primitives": [
            #        {
            #            "attributes": {
            #                "POSITION": accessor_index_vertices,
            #            },
            #            "indices": accessor_index_indices,
            #            "mode": WebGL.TRIANGLES,
            #            "material": material_index,
            #        }
            #    ],
            #}
            if meshLength != None:
                mesh["primitives"].append({
                    "attributes": {
                        "POSITION": accessor_index_vertices_wire,
                    },
                    "mode": WebGL.LINES,
                    "material": material_index_wire
                })
            #if name:
            #    mesh['name'] = name + str(mesh_index)
            mesh['name'] = self.getRealName(part.name) + str(mesh_index)
            self.gltf_dict['meshes'].append(mesh)
            info['meshes'].append((mesh_index, mesh))
            #mesh_index_group.append(mesh_index)
        
        # ----- Adding: nodes
        #mesh_index = mesh_index_group[k]
        with measure_time(log, 'nodes'):
            node_index = len(self.gltf_dict['nodes'])
            node = {
                "mesh": mesh_index,
            }
            #if name:
            #    node['name'] = name + str(mesh_index)
            node['name'] = self.getRealName(part.name) + str(mesh_index)
            node['extensions'] = {'TUOID': str(uuid.uuid4())}
            #if origin:
            #    node.update(self.coordsys_dict(part.world_coords - origin))
            node['matrix'] = getIdentityMatrix()#getArrayFromMatrix(part.localMatrix)#list(part.localMatrix.A)
            self.gltf_dict['nodes'].append(node)
            info['nodes'].append((node_index, node))

        # Appending child index to its parent's children list
        parent_node = self.gltf_dict['nodes'][parent_idx]
        parent_node['children'] = parent_node.get('children', []) + [node_index]

        return info

